package vcp.server.utility;

import java.util.Timer;
import java.util.TimerTask;

import org.joda.time.DateTime;
import org.joda.time.LocalDateTime;
import org.joda.time.Minutes;

import vcp.server.interfaces.IDateTimeDelegate;
import vcp.server.utility.timedecision.TimeDecision;

/**
 * 
 * @author VCP team
 *         <p>
 *         system clock class handles all time related logics , runs on server
 *         side and updates client side
 *         <p>
 */
public class SystemClock
{
	private static SystemClock clock;

	/**
	 * single tone
	 * 
	 * @return class instance
	 */
	public static synchronized SystemClock getClockInstance ()
	{
		if ( SystemClock.clock == null )
		{
			SystemClock.clock = new SystemClock();
		}
		return SystemClock.clock;
	}

	private DateTime systemDateTime;

	/**
	 * gets the system time and date
	 * 
	 * @return current systemDateTime
	 */
	public synchronized DateTime getSystemDateTime ()
	{
		return this.systemDateTime;
	}

	/**
	 * sets the time and date
	 * 
	 * @param systemDateTime
	 */
	private synchronized void setSystemDateTime ( DateTime systemDateTime )
	{
		this.systemDateTime = systemDateTime;
	}

	/**
	 * constructor to limit initialization to single tone
	 */
	private SystemClock ()
	{
	}

	/**
	 * Initializes system clock
	 * 
	 * @param dateTimeDelegate
	 * @param timeDecision
	 */
	public synchronized void initSystemClock ( final IDateTimeDelegate dateTimeDelegate, TimeDecision timeDecision )
	{
		// My date choose for debug
		this.systemDateTime = new DateTime(new LocalDateTime(2014, 1, 1, 0, 0, 0).toDateTime());
		final int increamentalInterval = timeDecision.getIncreamentalInterval();

		Timer task = new Timer();
		task.schedule(new TimerTask()
			{
				@Override
				public void run ()
				{
					SystemClock.this.setSystemDateTime(SystemClock.this.getSystemDateTime().plusMinutes(increamentalInterval));
					dateTimeDelegate.onDateTimeChange(SystemClock.this.getSystemDateTime());
				}
			}, 0, 1000 * 60);

	}

	/**
	 * check given date if its current or earlier
	 * 
	 * @param dateTime
	 *            - given date o be checked
	 * @return true if date is earlier else false
	 */
	public synchronized boolean isDateBeforeToday ( DateTime dateTime )
	{
		int delta = this.getDaysFromMinutesFormat(this.minutesBetweenCurrentMinute(dateTime));
		return delta < 0;
	}

	/**
	 * 
	 * @param dateTime
	 *            given date be checked
	 * @return true if the date is after current date
	 */
	public synchronized boolean isDateAfterToday ( DateTime dateTime )
	{
		int delta = this.getDaysFromMinutesFormat(this.minutesBetweenCurrentMinute(dateTime));
		return delta > 0;
	}

	/**
	 * 
	 * @param dateTime
	 *            given date to be checked
	 * @return true if the given date is current date
	 */
	public synchronized boolean isTodayDate ( DateTime dateTime )
	{
		int delta = this.getDaysFromMinutesFormat(this.minutesBetweenCurrentMinute(dateTime));
		return delta == 0;
	}

	/**
	 * 
	 * @param dateTime
	 *            given time to be verified
	 * @return true if given time is the current system time
	 */
	public synchronized boolean isCurrentTime ( DateTime dateTime )
	{
		int minDelta = this.minutesBetweenCurrentMinute(dateTime);
		int hourDelta = this.getHoursFromMinutesFormat(this.minutesBetweenCurrentMinute(dateTime));
		return ( minDelta == 0 ) && ( hourDelta == 0 );
	}

	/**
	 * 
	 * @param dateTime
	 *            given time to be verified
	 * @return true if given time is the current hour of the day
	 */
	public synchronized boolean isCurrentHourOfDay ( DateTime dateTime )
	{
		int delta = this.getHoursFromMinutesFormat(this.minutesBetweenCurrentMinute(dateTime));
		return delta == 0;
	}

	/**
	 * calculates delta between current date and given date
	 * 
	 * @param dateTime
	 *            as input date
	 * @return the delta
	 */
	public synchronized int daysBetweenCurrentDay ( DateTime dateTime )
	{
		int daysDelta = this.getDaysFromMinutesFormat(this.minutesBetweenCurrentMinute(dateTime));
		return daysDelta;
	}

	/**
	 * calculates difference in hours between given time and current time
	 * 
	 * @param dateTime
	 *            as input hour to be checked
	 * @return delta in hours
	 */
	public synchronized int hoursBetweenCurrentHour ( DateTime dateTime )
	{
		int hoursDelta = this.getHoursFromMinutesFormat(this.minutesBetweenCurrentMinute(dateTime));
		return hoursDelta;
	}

	/**
	 * calculates difference in minutes between given time and current time
	 * 
	 * @param dateTime
	 *            as input time to be checked
	 * @return delta in minutes
	 */
	public synchronized int minutesBetweenCurrentMinute ( DateTime dateTime )
	{
		int minutesDelta = Minutes.minutesBetween(this.getSystemDateTime(), dateTime).getMinutes();
		return minutesDelta;
	}

	/**
	 * adds the given number of days to current date
	 * 
	 * @param days
	 *            to be added
	 * @return the new calculated date
	 */
	public synchronized DateTime plusDays ( int days )
	{
		DateTime newDateTime = this.plusMinutes(this.getMinutesFromDaysFormat(days));
		return newDateTime;
	}

	/**
	 * adds the given number of hours to current date
	 * 
	 * @param hours
	 *            to be added
	 * @return the new calculated date
	 */
	public synchronized DateTime plusHours ( int hours )
	{
		DateTime newDateTime = this.plusMinutes(this.getMinutesFromHoursFormat(hours));
		return newDateTime;
	}

	/**
	 * adds the given number of minutes to current date
	 * 
	 * @param minutes
	 *            to be added
	 * @return the new calculated date
	 */
	public synchronized DateTime plusMinutes ( int minutes )
	{
		minutes = ( ( minutes / 10 ) + 1 ) * 10;
		DateTime newDateTime = this.getSystemDateTime().plusMinutes(minutes);
		return newDateTime;
	}

	/**
	 * Substitutes the given number of days to current date
	 * 
	 * @param days
	 *            to be substituted
	 * @return the new calculated date
	 */
	public synchronized DateTime minusDays ( int days )
	{
		DateTime newDateTime = this.minusMinutes(this.getMinutesFromDaysFormat(days));
		return newDateTime;
	}

	/**
	 * Substitutes the given number of hours to current date
	 * 
	 * @param hours
	 *            to be substituted
	 * @return the new calculated date
	 */
	public synchronized DateTime minusHours ( int hours )
	{
		DateTime newDateTime = this.minusMinutes(this.getMinutesFromHoursFormat(hours));
		return newDateTime;
	}

	/**
	 * Substitutes the given number of Minutes to current date
	 * 
	 * @param minutes
	 *            to be substituted
	 * @return the new calculated date
	 */
	public synchronized DateTime minusMinutes ( int minutes )
	{
		minutes = ( minutes / 10 ) * 10;
		DateTime newDateTime = this.getSystemDateTime().minusMinutes(minutes);
		return newDateTime;
	}

	/**
	 * converts from days to minutes
	 * 
	 * @param days
	 *            as number of days to be converted
	 * @return result in minutes
	 */
	private synchronized int getMinutesFromDaysFormat ( int days )
	{
		return days * 24 * 60;
	}

	/**
	 * converts from minutes to days
	 * 
	 * @param minutes
	 *            as number of minutes to be converted
	 * @return result in days
	 */
	private synchronized int getDaysFromMinutesFormat ( int minutes )
	{
		return minutes / ( 24 * 60 );
	}

	/**
	 * converts from hours to minutes
	 * 
	 * @param hours
	 * @return minutes
	 */
	private synchronized int getMinutesFromHoursFormat ( int hours )
	{
		return hours * 60;
	}

	/**
	 * converts from minutes to hours
	 * 
	 * @param minutes
	 * @return hours
	 */
	private synchronized int getHoursFromMinutesFormat ( int minutes )
	{
		return minutes / 60;
	}
}
